#include <ne.h>
#include <system/GlutApplication.h>

int Width = 1280;
int Height = 720;

float SkyTextureSize = 512.0f;
float SkyBoxSize = 1024.0f;

std::string skyBoxImageMap[6] = {"../image/skybox/stormy_RT.jpg",
                                 "../image/skybox/stormy_LF.jpg",
                                 "../image/skybox/stormy_UP.jpg",
                                 "../image/skybox/stormy_DN.jpg",
                                 "../image/skybox/stormy_BK.jpg",
                                 "../image/skybox/stormy_FR.jpg"};

int lineMode = 0;
int alphaMode = 0;
int lightOn = 1;
ne::Engine *pEngine = 0;
ne::Light *light = 0;
ne::Timer timer;
ne::Camera *camera = 0;
ne::Viewport *viewport = 0;
ne::GlutApplication *pApplication = 0;
ne::Mesh *pMesh = 0;
ne::GLFrameBufferObject *pFrameBuffer = 0;
ne::TexturePtr pTexture;
ne::TexturePtr pScreenTexture;
ne::TexturePtr pDepthTexture;
ne::TexturePtr pSkyTexture;
ne::GLSLProgramPtr pProgram;
ne::Matrix4 nowVPM;

ne::Vector2 mousePosition;
bool mouseLeftOn = false;
bool mouseRightOn = false;

int sampleLocation = 0;
int sampleNum = 9;
float point = 0.2f;
float range = 0.1f;

void mouseFunc(int button, int state, int x, int y);
void motionFunc(int x, int y);
void loop(void);
void redraw(void);
void drawFPS(void);
void resize(int width, int height);
void init();

int main(int argc, char *argv[]) /* I - Command-line arguments */
{
    pApplication = new ne::GlutApplication();//(argc, argv);
    glutReshapeFunc(resize);
    glutDisplayFunc(loop);
    glutMouseFunc(mouseFunc);
    glutMotionFunc(motionFunc);

    pEngine = pApplication->engine;
    if (!pEngine || !pEngine->isReady())
    {
        delete pApplication;
        return 0;
    }

    init();

    glutMainLoop();

    delete pApplication;

    return 0;
}

void init()
{
    glClearColor(0, 0, 0, 0);
    glClearDepth(1);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);

    glCullFace(GL_BACK);
    glEnable(GL_CULL_FACE);

    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);

    glShadeModel(GL_SMOOTH);
    glEnable(GL_POINT_SMOOTH);
    glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
    glEnable(GL_LINE_SMOOTH);
    glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
    glEnable(GL_POLYGON_SMOOTH);
    glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);

    pTexture = pEngine->getTextureManager()->createTextureFromFile("background", "../image/background.png", ne::TF_RGBA);
    pTexture->load();
    pTexture->setWrap(ne::TWP_REPEAT, ne::TWP_REPEAT);
    pTexture->setNumMipmap(8);
    pTexture->createRenderTexture();

    camera = new ne::Camera();
    viewport = new ne::Viewport(camera);
    camera->setPosition(ne::Vector3::UNIT_Z * pTexture->getWidth() * 2.0f);
    nowVPM = camera->getViewMatrix() * camera->getProjectionMatrix();

    // Volumetric Light
    pFrameBuffer = new ne::GLFrameBufferObject();
    pFrameBuffer->bind();

    pScreenTexture = pEngine->getTextureManager()->createTexture("FullScreen", "FullScreen", ne::TT_2D, ne::TF_RGBA, ne::PT_UNSIGNED_BYTE, Width, Height);
    pScreenTexture->createRenderTexture();
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pScreenTexture->getSource(), 0);

    pDepthTexture = pEngine->getTextureManager()->createTexture("DepthMap", "DepthMap", ne::TT_2D, ne::TF_DEPTH_COMPONENT, ne::PT_FLOAT, Width, Height);
    pDepthTexture->createRenderTexture();
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, pDepthTexture->getSource(), 0);

    std::cout << "Framebuffer " << pFrameBuffer->checkStatus() << std::endl;
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    ne::ImagePtr pImage;
    pSkyTexture = pEngine->getTextureManager()->createTexture("SkyBox", "SkyBox", ne::TT_CUBE_MAP, ne::TF_RGB, ne::PT_UNSIGNED_BYTE, SkyTextureSize, SkyTextureSize);
    for (size_t i=0; i<6; ++i)
    {
        pImage = pEngine->getImageManager()->create("SkyBoxImage", skyBoxImageMap[i]);
        pImage->load();
        pSkyTexture->loadImageForCubeMap(ne::TextureCubeMapFace(ne::TCMF_POSITIVE_X + i), *pImage);
        pEngine->getImageManager()->removeByPtr(pImage);
    }
    pSkyTexture->createRenderTexture();

    camera->setFarClipDistance(4096.0f);


    // Volumetric Light shader
    ne::GLSLManager *pManager = pEngine->getGLSLManager();
    pProgram = pManager->create("VolumetricLight", "../shader/volumetric_light.glpg", true);
    ne::GLSLShaderPtr pVShader;
    pVShader = pProgram->createShaderFromFile("VolumetricLightVertexShader", "../shader/volumetric_light.glvs", ne::SHADER_TYPE_VERTEX);
    ne::GLSLShaderPtr pFShader;
    pFShader = pProgram->createShaderFromFile("VolumetricLightFragmentShader", "../shader/volumetric_light.glfs", ne::SHADER_TYPE_FRAGMENT);
    pProgram->attachShader(pVShader);
    pProgram->attachShader(pFShader);
    pProgram->link();

    // Set light
    light = new ne::Light();
    light->setType(ne::LT_POINT);
    light->setAmbient(ne::Color::GRAY);
    light->setAttenuationFactor(ne::LAT_LINEAR, 0.01f);
    light->setPosition(ne::Vector3(0.0f, 0.0f, 0.0f));
}

void mouseFunc(int button, int state, int x, int y)
{
    if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
    {
        mousePosition.x = x;
        mousePosition.y = y;
        mouseLeftOn = true;
    }
    else if (button == GLUT_LEFT_BUTTON && state == GLUT_UP)
    {
        mouseLeftOn = false;
    }
    if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
    {
        mousePosition.x = x;
        mousePosition.y = y;
        mouseRightOn = true;
    }
    else if (button == GLUT_RIGHT_BUTTON && state == GLUT_UP)
    {
        mouseRightOn = false;
    }
}

void motionFunc(int x, int y)
{
    if (mouseLeftOn)
    {
        ne::Ray outRay;
        viewport->getScreenToWorldRay(ne::real(x), ne::real(y), outRay);
        ne::Plane p(ne::Vector3::UNIT_Z, 0.0f);
        ne::Vector3 point = p.projectVector(outRay.origin);
        ne::Vector3 d = point - outRay.origin;
        ne::real u = d.dotProduct(outRay.direction);
        point = outRay.origin + (d.squaredLength() / u) * outRay.direction;
        light->setPosition(point);
    }
    if (mouseRightOn)
    {
        float dx = x - mousePosition.x;
        float dy = y - mousePosition.y;
        camera->yaw(-0.01f * dx * ne::Math::PI / 36, ne::TS_WORLD);
        camera->pitch(-0.01f * dy * ne::Math::PI / 36);
        mousePosition.x = x;
        mousePosition.y = y;
    }
}

void loop(void)
{
    ne::Keyboard *pKeyboard = pEngine->getKeyboard();
    ne::real speed = 0.1f;
    if (pKeyboard->isKeyDown(ne::KK_F12))
    {
        speed = 2.0f;
    }
    if (pKeyboard->isKeyDown(ne::KK_W))
    {
        camera->translate(speed * ne::Vector3::NEGATIVE_UNIT_Z, ne::TS_LOCAL);
    }
    if (pKeyboard->isKeyDown(ne::KK_S))
    {
        camera->translate(speed * ne::Vector3::UNIT_Z, ne::TS_LOCAL);
    }
    if (pKeyboard->isKeyDown(ne::KK_A))
    {
        camera->translate(speed * ne::Vector3::NEGATIVE_UNIT_X, ne::TS_LOCAL);
    }
    if (pKeyboard->isKeyDown(ne::KK_D))
    {
        camera->translate(speed * ne::Vector3::UNIT_X, ne::TS_LOCAL);
    }
    if (pKeyboard->isKeyDown(ne::KK_Z))
    {
        camera->translate(speed * ne::Vector3::NEGATIVE_UNIT_Y, ne::TS_PARENT);
    }
    if (pKeyboard->isKeyDown(ne::KK_Q))
    {
        camera->translate(speed * ne::Vector3::UNIT_Y, ne::TS_PARENT);
    }
    if (pKeyboard->isKeyDown(ne::KK_ESC))
    {
        exit(1);
    }
    redraw();
}

void drawSky(void)
{
    glDisable(GL_DEPTH_TEST);
    glDepthMask(GL_FALSE);
    glDisable(GL_LIGHTING);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(pSkyTexture->getType(), pSkyTexture->getSource());
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    glEnable(pSkyTexture->getType());

    glBegin(GL_QUADS);
        glTexCoord3f(-1.0f, 1.0f, 1.0f);
        glVertex3f(-SkyBoxSize, SkyBoxSize, SkyBoxSize);
        glTexCoord3f(-1.0f, -1.0f, 1.0f);
        glVertex3f(-SkyBoxSize, -SkyBoxSize, SkyBoxSize);
        glTexCoord3f(-1.0f, -1.0f, -1.0f);
        glVertex3f(-SkyBoxSize, -SkyBoxSize, -SkyBoxSize);
        glTexCoord3f(-1.0f, 1.0f, -1.0f);
        glVertex3f(-SkyBoxSize, SkyBoxSize, -SkyBoxSize);

        glTexCoord3f(1.0f, 1.0f, 1.0f);
        glVertex3f(SkyBoxSize, SkyBoxSize, SkyBoxSize);
        glTexCoord3f(1.0f, 1.0f, -1.0f);
        glVertex3f(SkyBoxSize, SkyBoxSize, -SkyBoxSize);
        glTexCoord3f(1.0f, -1.0f, -1.0f);
        glVertex3f(SkyBoxSize, -SkyBoxSize, -SkyBoxSize);
        glTexCoord3f(1.0f, -1.0f, 1.0f);
        glVertex3f(SkyBoxSize, -SkyBoxSize, SkyBoxSize);

        glTexCoord3f(-1.0f, -1.0f, -1.0f);
        glVertex3f(-SkyBoxSize, -SkyBoxSize, -SkyBoxSize);
        glTexCoord3f(-1.0f, -1.0f, 1.0f);
        glVertex3f(-SkyBoxSize, -SkyBoxSize, SkyBoxSize);
        glTexCoord3f(1.0f, -1.0f, 1.0f);
        glVertex3f(SkyBoxSize, -SkyBoxSize, SkyBoxSize);
        glTexCoord3f(1.0f, -1.0f, -1.0f);
        glVertex3f(SkyBoxSize, -SkyBoxSize, -SkyBoxSize);

        glTexCoord3f(-1.0f, 1.0f, -1.0f);
        glVertex3f(-SkyBoxSize, SkyBoxSize, -SkyBoxSize);
        glTexCoord3f(1.0f, 1.0f, -1.0f);
        glVertex3f(SkyBoxSize, SkyBoxSize, -SkyBoxSize);
        glTexCoord3f(1.0f, 1.0f, 1.0f);
        glVertex3f(SkyBoxSize, SkyBoxSize, SkyBoxSize);
        glTexCoord3f(-1.0f, 1.0f, 1.0f);
        glVertex3f(-SkyBoxSize, SkyBoxSize, SkyBoxSize);

        glTexCoord3f(1.0f, 1.0f, -1.0f);
        glVertex3f(SkyBoxSize, SkyBoxSize, -SkyBoxSize);
        glTexCoord3f(-1.0f, 1.0f, -1.0f);
        glVertex3f(-SkyBoxSize, SkyBoxSize, -SkyBoxSize);
        glTexCoord3f(-1.0f, -1.0f, -1.0f);
        glVertex3f(-SkyBoxSize, -SkyBoxSize, -SkyBoxSize);
        glTexCoord3f(1.0f, -1.0f, -1.0f);
        glVertex3f(SkyBoxSize, -SkyBoxSize, -SkyBoxSize);

        glTexCoord3f(1.0f, 1.0f, 1.0f);
        glVertex3f(SkyBoxSize, SkyBoxSize, SkyBoxSize);
        glTexCoord3f(1.0f, -1.0f, 1.0f);
        glVertex3f(SkyBoxSize, -SkyBoxSize, SkyBoxSize);
        glTexCoord3f(-1.0f, -1.0f, 1.0f);
        glVertex3f(-SkyBoxSize, -SkyBoxSize, SkyBoxSize);
        glTexCoord3f(-1.0f, 1.0f, 1.0f);
        glVertex3f(-SkyBoxSize, SkyBoxSize, SkyBoxSize);
    glEnd();
    glDisable(pSkyTexture->getType());

    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);
    glEnable(GL_LIGHTING);
}

void drawMesh(void)
{
    pTexture = pEngine->getTextureManager()->getByName("background");
    glBindTexture(pTexture->getType(), pTexture->getSource());
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glEnable(pTexture->getType());
    ne::real w = pTexture->getWidth();
    ne::real h = pTexture->getHeight();

    glPushMatrix();
        glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
        glBegin(GL_QUADS);
        {
            glTexCoord2f(0.0f, 1.0f);
            glVertex3f(-w, h, 0);
            glTexCoord2f(0.0f, 0.0f);
            glVertex3f(-w, -h, 0);
            glTexCoord2f(1.0f, 0.0f);
            glVertex3f(w, -h, 0);
            glTexCoord2f(1.0f, 1.0f);
            glVertex3f(w, h, 0);
        }
        glEnd();
    glPopMatrix();
    glDisable(pTexture->getType());
}

void redraw(void)
{
    glEnable(GL_FRAMEBUFFER);
    pFrameBuffer->bind();

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    const ne::Matrix4 &viewMatrix = camera->getViewMatrix();
    nowVPM = viewMatrix * camera->getProjectionMatrix();
    glLoadMatrixf(viewMatrix.ptr());

    glPushMatrix();
        ne::Vector3 pos = camera->getPosition();
        glTranslatef(pos.x, pos.y, pos.z);
        drawSky();
    glPopMatrix();

    glEnable(GL_BLEND);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_COLOR_SUM);
    glDisable(GL_LIGHTING);

    drawMesh();

    glDisable(GL_BLEND);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_COLOR_MATERIAL);
    glDisable(GL_COLOR_SUM);

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glDisable(GL_FRAMEBUFFER);

    drawFPS();

    glutSwapBuffers();
    glutPostRedisplay();
}

void resize(int width, int height)
{
    /* Save the new width and height */
    Width  = width;
    Height = height;

    /* Reset the viewport->.. */
    glViewport(0, 0, width, height);
    viewport->setPosition(0, 0);
    viewport->resize(width, height);

    glMatrixMode(GL_PROJECTION);
    const ne::Matrix4 &projectionMatrix = camera->getProjectionMatrix();
    glLoadMatrixf(projectionMatrix.ptr());
    //gluPerspective(45, double(width)/double(height), 1.0, 1000.0);
    glMatrixMode(GL_MODELVIEW);
}

void drawFPS()
{
    //Set matrices for ortho
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrtho(0.0f, viewport->getWidth(),
            viewport->getHeight(), 0.0f,
            1.0f, -1.0f);

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();

    // Volumetric Light pass
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    pProgram->useProgram();
    ne::Vector3 lightPos3 = light->getPosition();
    ne::Vector4 lightPos4(lightPos3.x, lightPos3.y, lightPos3.z, 1.0f);
    lightPos4 = lightPos4 * nowVPM;
    if (lightPos4.w > 0.0f)
    {
        lightPos4 /= lightPos4.w;
    }
    lightPos4 = 0.5f * (lightPos4 + 1.0f);
    ne::Vector2 lightPos2(lightPos4.x, lightPos4.y);
    pProgram->setUniform("screenLightPos", lightPos2);
    pProgram->setUniform("screenMap", 0);
    //pProgram->setUniform("depthMap", 1);
    pProgram->validate();

    glColor4f(1.0, 1.0, 1.0, 1.0);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(pScreenTexture->getType(), pScreenTexture->getSource());
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glEnable(pScreenTexture->getType());

    /*
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(pDepthTexture->getType(), pDepthTexture->getSource());
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glEnable(pDepthTexture->getType());
    */

    glBegin(GL_QUADS);
    {
        glTexCoord2f( 0.0f, 1.0f);
        glVertex3f(0, 0, 0);
        glTexCoord2f( 0.0f, 0.0f);
        glVertex3f(0, 720, 0);
        glTexCoord2f( 1.0f, 0.0f);
        glVertex3f(1280, 720, 0);
        glTexCoord2f( 1.0f, 1.0f);
        glVertex3f(1280, 0, 0);
    }
    glEnd();
    glDisable(pTexture->getType());
    pProgram->useProgram(false);
    glActiveTexture(GL_TEXTURE0);

	//Print text
    glEnable(GL_BLEND);
    
    //Print text
    ne::FontPtr font = pEngine->getFont();
    ne::Vector3 pos(Width - 50.0f, 10.0f, 0.0f);
    float w = font->getFontWidth();
    float h = font->getFontHeight();

    char fpsString[32];
    ne::real interval = timer.getIntervalMilliseconds();
	sprintf(fpsString, "%.2f", 1000.0f / interval);

    font->renderText2D(fpsString, pos, 6.0f*w, h, ne::Color(0.8f, 1.0f, 0.0f, 1.0f));

    //reset matrices
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}

